package lexico;

import java.io.IOException;
import java.io.InputStream;

import utils.Token;

enum EstadoLexico {
	INICIAL, RECMAS, RECMENOS, RECPOR, RECDIV, RECASIG, RECIMPL, RECPA, RECPC, RECC, RECNUM, RECIDEN, RECZERO, RECPyC, RECAps, RECFIN, INITNUM, INITASIG, INITC, RECCOM,
	// ---------- nuevos --------------- //
	INITARRAY, RECLLA, RECLLC, RECCOMA, RECPUNTERO, RECIGUAL, RECPUNTO
};

// ------------------------- //

public class AnalizadorLexico {
	private EstadoLexico estado;
	private int filaInicio;
	private int fila;
	private int colInicio;
	private int col;
	private String lexema;
	private int caracter;
	private InputStream input;
	private static final int EOF = -1;

	public AnalizadorLexico(InputStream i) throws IOException {
		input = i;
		fila = 0;
		col = -1;
		caracter = this.sigCar();
	}

	public Token nextToken() throws IOException {
		estado = EstadoLexico.INICIAL;
		filaInicio = fila;
		colInicio = col;
		lexema = "";

		while (true) {
			switch (estado) {
			case INICIAL:
				if (esNumPos(caracter))
					transita(EstadoLexico.RECNUM);
				else if (esComienzoIden(caracter))
					transita(EstadoLexico.RECIDEN);
				else if (caracter == '+')
					transita(EstadoLexico.RECMAS);
				else if (caracter == '-')
					transita(EstadoLexico.RECMENOS);
				else if (caracter == '*')
					transita(EstadoLexico.RECPOR);
				else if (caracter == '/')
					transita(EstadoLexico.RECDIV);
				else if (caracter == '0')
					transita(EstadoLexico.RECZERO);
				else if (caracter == '(')
					transita(EstadoLexico.RECPA);
				else if (caracter == ')')
					transita(EstadoLexico.RECPC);
				else if (caracter == '[')
					transita(EstadoLexico.INITC);
				else if (caracter == ';')
					transita(EstadoLexico.RECPyC);
				else if (caracter == '&')
					transita(EstadoLexico.RECAps);
				else if (caracter == ':')
					transita(EstadoLexico.INITASIG);
				else if (caracter == '@')
					transita(EstadoLexico.RECCOM);
				// ---------- nuevos --------------- //
				else if (caracter == '{')
					transita(EstadoLexico.RECLLA);
				else if (caracter == '}')
					transita(EstadoLexico.RECLLC);
				else if (caracter == ',')
					transita(EstadoLexico.RECCOMA);
				else if (caracter == '=')
					transita(EstadoLexico.RECIGUAL);
				else if (caracter == '^')
					transita(EstadoLexico.RECPUNTERO);
				else if (caracter == '.')
					transita(EstadoLexico.RECPUNTO);
				// ---------------------------- //
				else if (caracter == EOF)
					transita(EstadoLexico.RECFIN);
				else if (esSeparador(caracter))
					transitaIgnorando(EstadoLexico.INICIAL);
				else
					errorLexico();
				break;
			case RECZERO:
				return new Token(filaInicio, colInicio, CatLexica.NUM, lexema);
			case RECMAS:
				return new Token(filaInicio, colInicio, CatLexica.MAS);
			case RECMENOS:
				if (caracter == '>')
					transita(EstadoLexico.RECIMPL);
				else
					return new Token(filaInicio, colInicio, CatLexica.MENOS);
				break;
			case RECPOR:
				return new Token(filaInicio, colInicio, CatLexica.POR);
			case RECDIV:
				return new Token(filaInicio, colInicio, CatLexica.DIV);
			case INITASIG:
				if (caracter == '=')
					transita(EstadoLexico.RECASIG);
				else
					errorLexico();
			case RECASIG:
				return new Token(filaInicio, colInicio, CatLexica.ASIG);
			case RECIMPL:
				return new Token(filaInicio, colInicio, CatLexica.IMPL);
			case RECPA:
				return new Token(filaInicio, colInicio, CatLexica.PA);
			case RECPC:
				return new Token(filaInicio, colInicio, CatLexica.PC);
			case INITC:
				if (caracter == ']')
					transita(EstadoLexico.RECC);
				else if (esComienzoIden(caracter) || esDigito(caracter))
					transita(EstadoLexico.INITARRAY);
				else
					errorLexico();
				break;
			case RECC:
				return new Token(filaInicio, colInicio, CatLexica.C);
			case RECPyC:
				return new Token(filaInicio, colInicio, CatLexica.PyC);
			case RECAps:
				return new Token(filaInicio, colInicio, CatLexica.APS);
			case RECFIN:
				return new Token(filaInicio, colInicio, CatLexica.EOF);
			case RECIDEN:
				if (esComienzoIden(caracter) || esDigito(caracter))
					transita(EstadoLexico.RECIDEN);
				else
					return reservadaOlexema();
				break;
			case RECNUM:
				if (esDigito(caracter))
					transita(EstadoLexico.RECNUM);
				else
					return new Token(filaInicio, colInicio, CatLexica.NUM,
							lexema);
				break;
			// ---------- nuevos --------------- //
			case INITARRAY:
				if (esComienzoIden(caracter) || esDigito(caracter))
					transita(EstadoLexico.INITARRAY);
				else
					return reservadaOlexema();
				break;
			case RECLLA:
				return new Token(filaInicio, colInicio, CatLexica.LLA);
			case RECLLC:
				return new Token(filaInicio, colInicio, CatLexica.LLC);
			case RECCOMA:
				return new Token(filaInicio, colInicio, CatLexica.COMA);
			case RECPUNTERO:
				return new Token(filaInicio, colInicio, CatLexica.PUNTERO);
			case RECIGUAL:
				return new Token(filaInicio, colInicio, CatLexica.IGUAL);
			case RECPUNTO:
				return new Token(filaInicio, colInicio, CatLexica.PUNTO);
				// ---------------------------- //
			case RECCOM:
				if (caracter != '\n' && caracter != EOF)
					transitaIgnorando(EstadoLexico.RECCOM);
				else if (caracter == '\n')
					transitaIgnorando(EstadoLexico.INICIAL);
				else
					transitaIgnorando(EstadoLexico.RECFIN); // Si llega caracter
															// == EOF
			}

		}

	}

	private Token reservadaOlexema() {
		if (lexema.equals("if"))
			return new Token(filaInicio, colInicio, CatLexica.IF);
		else if (lexema.equals("fi"))
			return new Token(filaInicio, colInicio, CatLexica.FI);
		else if (lexema.equals("do"))
			return new Token(filaInicio, colInicio, CatLexica.DO);
		else if (lexema.equals("od"))
			return new Token(filaInicio, colInicio, CatLexica.OD);
		else if (lexema.equals("case"))
			return new Token(filaInicio, colInicio, CatLexica.CASE);
		else if (lexema.equals("true"))
			return new Token(filaInicio, colInicio, CatLexica.TRUE);
		else if (lexema.equals("false"))
			return new Token(filaInicio, colInicio, CatLexica.FALSE);
		else if (lexema.equals("int"))
			return new Token(filaInicio, colInicio, CatLexica.INT);
		else if (lexema.equals("boolean"))
			return new Token(filaInicio, colInicio, CatLexica.BOOLEAN);
		else if (lexema.equals("and"))
			return new Token(filaInicio, colInicio, CatLexica.AND);
		else if (lexema.equals("or"))
			return new Token(filaInicio, colInicio, CatLexica.OR);
		else if (lexema.equals("not"))
			return new Token(filaInicio, colInicio, CatLexica.NOT);
		else if (lexema.equals("eq"))
			return new Token(filaInicio, colInicio, CatLexica.EQ);
		else if (lexema.equals("neq"))
			return new Token(filaInicio, colInicio, CatLexica.NEQ);
		else if (lexema.equals("lt"))
			return new Token(filaInicio, colInicio, CatLexica.LT);
		else if (lexema.equals("le"))
			return new Token(filaInicio, colInicio, CatLexica.LE);
		else if (lexema.equals("gt"))
			return new Token(filaInicio, colInicio, CatLexica.GT);
		else if (lexema.equals("ge"))
			return new Token(filaInicio, colInicio, CatLexica.GE);
		// ---------- nuevos --------------- //
		else if (lexema.equals("new"))
			return new Token(filaInicio, colInicio, CatLexica.NEW);
		else if (lexema.equals("null"))
			return new Token(filaInicio, colInicio, CatLexica.NULL);
		else if (lexema.equals("dispose"))
			return new Token(filaInicio, colInicio, CatLexica.DISPOSE);
		else if (lexema.equals("tipo"))
			return new Token(filaInicio, colInicio, CatLexica.TYPE);
		else if (lexema.equals("proc"))
			return new Token(filaInicio, colInicio, CatLexica.PROC);
		else if (lexema.equals("record"))
			return new Token(filaInicio, colInicio, CatLexica.RECORD);
		else if (lexema.equals("array"))
			return new Token(filaInicio, colInicio, CatLexica.ARRAY);
		else if (lexema.equals("of"))
			return new Token(filaInicio, colInicio, CatLexica.OF);
		else if (lexema.equals("for"))
			return new Token(filaInicio, colInicio, CatLexica.FOR);
		else if (lexema.equals("var"))
			return new Token(filaInicio, colInicio, CatLexica.VAR);
		else if (lexema.equals("to"))
			return new Token(filaInicio, colInicio, CatLexica.TO);
		else if (lexema.equals("while"))
			return new Token(filaInicio, colInicio, CatLexica.WHILE);
		// ---------------------------- //
		else
			return new Token(filaInicio, colInicio, CatLexica.IDEN, lexema);
	}

	private void transita(EstadoLexico aEstado) throws IOException {
		lexema = lexema + (char) caracter;
		transitaIgnorando(aEstado);
	}

	private void transitaIgnorando(EstadoLexico aEstado) throws IOException {
		estado = aEstado;
		filaInicio = fila;
		colInicio = col;
		caracter = sigCar();

	}

	private int sigCar() throws IOException {
		caracter = input.read();
		if (caracter == '\n') {
			col = -1;
			fila++;
		} else
			col++;
		return caracter;
	}

	private boolean esComienzoIden(int c) {
		return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'));
	}

	private boolean esSeparador(int c) {
		return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
	}

	private boolean esNumPos(int c) {
		return (c >= '1' && c <= '9');
	}

	private boolean esDigito(int c) {
		return (esNumPos(c) || c == '0');
	}

	private void errorLexico() {
		System.err.println("(" + fila + "," + col + ")"
				+ "ERROR LEXICO: caracter desconocido: " + (char) caracter);
		System.exit(1);

	}
	/*
	 * public static void main(String[] args) throws IOException {
	 * AnalizadorLexico alex = new AnalizadorLexico(System.in); Token t; do { t
	 * = alex.nextToken(); System.out.println(t); } while(t.getCategoria() !=
	 * CatLexica.EOF); }
	 */
}
